package at.ac.tuwien.lsds.schedule.strategy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Random;

import org.apache.log4j.Logger;

import at.ac.tuwien.lsds.exception.MachineException;
import at.ac.tuwien.lsds.exception.SchedulerException;
import at.ac.tuwien.lsds.federation.CloudPartner;
import at.ac.tuwien.lsds.model.Application;
import at.ac.tuwien.lsds.model.PhysicalMachine;
import at.ac.tuwien.lsds.model.PhysicalMachineImpl;
import at.ac.tuwien.lsds.schedule.strategy.SchedulerC.SchedulerEvolution.SimplifiedPhysicalMaschine;
import at.ac.tuwien.lsds.util.SchedulerOutputFile;
import at.ac.tuwien.lsds.util.Settings;

// TODO:
// Initiale Resource-Kosten als Konstanten
// SimplifiedPhysicalMaschine[] -> kapseln als Individuum
// Random in Utils-Klasse auslagern.

public class SchedulerC extends ScheduleStrategyImpl{
	
	private static final Logger log = Logger.getLogger(SchedulerC.class);
	
	private static final Integer MaxPopulationSize = 100;
	private static final Integer MinPopulationSize = 5;
	private static final Integer MaxGenerations = 20;
	private static final Float WeightHdd = 0.2F;
	private static final Float WeightCpu = 0.4F;
	private static final Float WeightRam = 0.4F;
	
	public SchedulerC(){
		pms = new ArrayList<PhysicalMachine>();
		queue = new ArrayList<Application>();
		
//		SchedulerOutputFile.getInstance().setScenario(Settings.getScenario().toString());
		SchedulerOutputFile.getInstance().setScheduler("C");
	}
	
	@Override
	public void runApplication(Application app) {
		Boolean isScheduled = schedule(app);
		
		if(!isScheduled){
			queue.add(app);
		}
	}

	@Override
	public void runApplication(Application app, Boolean queueing) throws SchedulerException {
		Boolean isScheduled = schedule(app);
		
		if(!isScheduled){
			if(queueing){
				queue.add(app);
			} else {
				throw new SchedulerException("cannot schedule the Application. Not enough resources");	
			}
		}
	}

	@Override
	public void update(Observable o, Object arg) {
		PhysicalMachine pm = (PhysicalMachine)o;
		
		if(0 == pm.getRunningVMCount()){
			pm.shutdown();
			pms.remove(pm);
			
			log.debug("Shutdown a Physical Machine - PM Count:" + pms.size());
		}
		
		scheduleQueue();
	}
	
	private boolean schedule(Application app){
		SchedulerEvolution evolution = new SchedulerEvolution(MaxPopulationSize, MinPopulationSize, MaxGenerations, WeightHdd, WeightCpu, WeightRam);
		Boolean isApplied = false;
		
		// Wenn es mehr als 0 physikalische Maschinen gibt und
		// die Anzahl der verwendeten physikalischen Maschinen 0 ist,
		// dann starte eine neue physikalische Maschine.
		if(0 < Settings.getPmCount() && pms.size() == 0){
			// Neue Maschine erzeugen
			PhysicalMachine pm = new PhysicalMachineImpl();
			
			// Neue Maschine beobachen
			((PhysicalMachineImpl)pm).addObserver(this);
			
			// Statistik aktualisieren
			totalPms++;
			totalVms++;
			
			// Maschine zur Liste hinzuf�gen
			pms.add(pm);
		}
		
		isApplied = tryToScheduleApplication(app, evolution);
		
		if(isApplied){
			return true;
		}
		
		if(Settings.getPmCount() > pms.size()){
			PhysicalMachine new_pm = new PhysicalMachineImpl();
			
			((PhysicalMachineImpl)new_pm).addObserver(this);
			
			// Statistik aktualisieren
			totalPms++;
			totalVms++;
			
			pms.add(new_pm);
			
			isApplied = tryToScheduleApplication(app, evolution);
			
			
		}
		
		if(isApplied){
			return true;
		}
		
		// Wenn die Applikation nicht angenommen werden kann,
		// wird sie an einen Partner ausgelagert.
		CloudPartner cp = new CloudPartner();
		
		for(int i = 0; i < Settings.getCloudPartnerCount(); i++){
			if(cp.sendApplication(app)){
				totalOutSourced++;
				
				return true;
			}
		}
		
		return false;
	}

	private Boolean tryToScheduleApplication(Application app, SchedulerEvolution evolution) {
		SimplifiedPhysicalMaschine[] individual;
		ArrayList<Application> applicationsToSchedule = null;
		
		Boolean isApplied = false;
		
		// Hole alle Applikationen
		applicationsToSchedule = new ArrayList<Application>();
	
		for (PhysicalMachine pm : pms){
			applicationsToSchedule.addAll(pm.getApplications());
		}
		
		// F�ge neue Applikation hinzu
		applicationsToSchedule.add(app);
		
		individual = evolution.getFittestIndividual(pms.size(), applicationsToSchedule);
		
		if (individual != null) {
			
			// Entferne alle Applikationen von allen physikalischen Maschinen
			for (PhysicalMachine pm : pms){
				pm.removeAllApplication();
			}
			
			// Plan muss mit der aktuellen Konfiguration stimmen
			assert(pms.size() == individual.length);
			
			try {
				
				// Weise alle Applikationen laut neuem Scheduling-Plan zu
				for (int machineIndex = 0; machineIndex < pms.size(); machineIndex++) {
					PhysicalMachine pm = pms.get(machineIndex);
					SimplifiedPhysicalMaschine spm = individual[machineIndex];
					
					if (spm != null && spm.apps != null && spm.apps.size() > 0) {
						Integer overallNeededHdd = 0;
						Integer overallNeededRam = 0;
						Integer overallNeededCpu = 0;
						
						overallNeededHdd = spm.usedHdd;
						overallNeededRam = spm.usedRam;
						overallNeededCpu = spm.usedCpu;
						
						if (pm.getRunningVMCount() == 0) {
							pm.startVM(overallNeededHdd, overallNeededRam, overallNeededCpu);
						} else {
							pm.setVMResources(overallNeededHdd, overallNeededRam, overallNeededCpu);
						}
						
						for (Application appFromIndividual : spm.apps){
							pm.addApplication(appFromIndividual, false);
						}
					} else {
						if(0 == pm.getRunningVMCount()){
							pm.shutdown();
							pms.remove(pm);
							
							log.debug("Shutdown a Physical Machine - PM Count:" + pms.size());
						} else {
							throw new MachineException("Physical machines has virtual machines running with no application!");
						}
						
						log.info("SimplifiedPhysicalMaschine not used in this individual.");
					}
				}
				
				isApplied = true;
			} catch (MachineException e){
				log.error(e.getMessage());
			}
		} else {
			log.info("Kein fittes Individuum gefunden.");
		}
		
		return isApplied;
	}

	private void scheduleQueue(){
		Iterator<Application> iterator = queue.iterator();
		List<Application> rem = new ArrayList<Application>();
		
		while(iterator.hasNext()){
			Application app = iterator.next();
			
			if(schedule(app)){
				rem.add(app);
			}
		}
		
		queue.removeAll(rem);
	}

	public class SchedulerEvolution {
	
		private Integer maxPopulationSize = 100;
		private Integer minPopulationSize = 5;
		private Integer maxGenerations = 10;
		private Float weightHdd = 0.2F;
		private Float weightCpu = 0.4F;
		private Float weightRam = 0.4F;
		
		public SchedulerEvolution()
		{
			// Verwendet die Default-Werte
		}
		
		public SchedulerEvolution(Integer maxPopulationSize, Integer minPopulationSize, Integer maxGenerations, Float weightHdd, Float weightCpu, Float weightRam){
			this.maxPopulationSize = maxPopulationSize;
			this.minPopulationSize = minPopulationSize;
			this.maxGenerations = maxGenerations;
			
			this.weightHdd = weightHdd;
			this.weightCpu = weightCpu;
			this.weightRam = weightRam;
		}
		
		/**
		 * Method returns the fittest individual.
		 * @param currentlyUsedPMs
		 * @param applicationsToSchedule
		 * @return
		 */
		public SimplifiedPhysicalMaschine[] getFittestIndividual(int currentlyUsedPMs, ArrayList<Application> applicationsToSchedule){
			SimplifiedPhysicalMaschine[] fittestIndividual = null;
			
			ArrayList<SimplifiedPhysicalMaschine[]> initialPopulation = null;
			ArrayList<SimplifiedPhysicalMaschine[]> currentPopulation = null;
			
			Map<SimplifiedPhysicalMaschine[], Float> evaluatedPopulation = null;
			
			Float currentlyBestFitnessValue = 0F;
			
			Integer appCounterBeforeExecution = 0;
			Integer appCounterAfterExecution = 0;
			
			// Count apps
			appCounterBeforeExecution = applicationsToSchedule.size();
			
			initialPopulation = getInitialPopulation(maxPopulationSize, currentlyUsedPMs, applicationsToSchedule);
			
			if (initialPopulation != null && initialPopulation.size() > 0) {
				currentPopulation = initialPopulation;
				
				// TODO: Optimierung, wenn sich die Population nicht ge�ndert hat, kann
				// die Schleife sofort abgebrochen werden.
				for (int i = 0; i < maxGenerations; i++){
					evaluatedPopulation = evaluation(currentPopulation);
					
					currentPopulation = selection(evaluatedPopulation);
					
					currentPopulation = reproduction(maxPopulationSize, currentPopulation);
				}
				
				// Finde das fittesten Individuum
				evaluatedPopulation = evaluation(currentPopulation);
				
				for (Map.Entry<SimplifiedPhysicalMaschine[], Float> individual : evaluatedPopulation.entrySet()){
					if (individual.getValue() > currentlyBestFitnessValue){
						currentlyBestFitnessValue = individual.getValue();
						fittestIndividual = individual.getKey();
					}
				}
			} else {
				// This happens if there are to many applications for the currently running pms.
				fittestIndividual = null;
			}
			
			// Check if no applications got lost
			if (fittestIndividual != null){
				for(SimplifiedPhysicalMaschine spm : fittestIndividual){
					appCounterAfterExecution += spm.getApplications().size();
				}
				
				if (!appCounterAfterExecution.equals(appCounterBeforeExecution)){
					throw new IllegalArgumentException("Post-Condition: Apps gone missin!");
				}
			}
			
			return fittestIndividual;
		}
		
		public ArrayList<SimplifiedPhysicalMaschine[]> getInitialPopulation(Integer maxPopulationSize, Integer currentlyUsedPMs, ArrayList<Application> applicationsToSchedule){
			ArrayList<SimplifiedPhysicalMaschine[]> population = null;
			SimplifiedPhysicalMaschine[] individual = null;
			Integer currentPopulationTry = 0;
			
			// Initialisiere Population
			population = new ArrayList<SimplifiedPhysicalMaschine[]>();
			
			// Erzeuge Population
			while (currentPopulationTry < maxPopulationSize){
				// Erzeuge ein zuf�lliges Individuum
				individual = getRandomIndividual(currentlyUsedPMs, applicationsToSchedule);
				
				// Wenn das Individuum lebensf�hig ist, f�ge es der Population hinzu
				if (individual != null){
					population.add(individual);
				} else {
					
				}
				
				currentPopulationTry++;
			}
			
			// Check post condition
//			if (!(population != null && population.size() > 0)){
//				throw new IllegalArgumentException("Post-Condition: Population was null or empty!");
//			}
			
			return population;
		}
		
		public Map<SimplifiedPhysicalMaschine[], Float> evaluation(ArrayList<SimplifiedPhysicalMaschine[]> population){
			Map<SimplifiedPhysicalMaschine[], Float> evaluatedPopulation = null;
			
			Float utilizationHdd = 0F; // in Prozent
			Float utilizationRam = 0F; // in Prozent
			Float utilizationCpu = 0F; // in Prozent
			
			Float overallMaschineUtilization = 0F; // in Prozent
			Float overallMaschineFitness = 0F; // abstrakter Fitnesswert (Wertebereich: 1 - 2)
			
			Float overallIndividualFitness = 0F; // in Prozent
			
			// Check pre condition
			if (!(population != null && population.size() > 0)){
				throw new IllegalArgumentException("Population was null or empty!");
			}
			
			if (!(weightHdd + weightRam + weightCpu == 1F)) {
				throw new IllegalArgumentException("Weight factors sum not up to 1!");
			}
			
			evaluatedPopulation = new HashMap<SimplifiedPhysicalMaschine[], Float>();
			
			for (SimplifiedPhysicalMaschine[] individual : population){
				overallIndividualFitness = 0F;
				
				for (SimplifiedPhysicalMaschine pm : individual) {
					if (pm != null)
					{
						// Berechne Auslastung
						utilizationHdd = pm.getUtilizationOfHdd(); // 0 <<< 1 ... Werte n�her bei 1 ist besser
						utilizationRam = pm.getUtilizationOfRam(); // 0 <<< 1 ... Werte n�her bei 1 ist besser
						utilizationCpu = pm.getUtilizationOfCpu(); // 0 <<< 1 ... Werte n�her bei 1 ist besser
						
						// Berechne Gesamtauslastung der Maschine
						overallMaschineUtilization =
								utilizationHdd * weightHdd + 
								utilizationRam * weightRam +
								utilizationCpu * weightCpu;
						
						// Berechne die Fitness der Maschine (Leere Maschinen sind auch sehr gut)
						overallMaschineFitness =
							overallMaschineUtilization == 0F ? 2F : overallMaschineUtilization + 1F;
						
						// Addiere den Fitnesswert der Maschine zur Gesamtfitness
						// Bessere Auslastung wird h�her gewichtet, indem man den Fitnesswert noch mit 2 potenziert.
						overallIndividualFitness += new Float(Math.pow((double)overallMaschineFitness, 2D));
					} else {
						assert(false);
					}
				}
				
				evaluatedPopulation.put(individual, overallIndividualFitness);
			}
			
			// Check post condition
			if (!(evaluatedPopulation != null && evaluatedPopulation.size() > 0)){
				throw new IllegalArgumentException("EvaluatedPopulation was null or empty");
			}
			
			return evaluatedPopulation;
		}
		
		public ArrayList<SimplifiedPhysicalMaschine[]> selection(Map<SimplifiedPhysicalMaschine[], Float> evaluatedPopulation){
			ArrayList<SimplifiedPhysicalMaschine[]> selectedPopulation = null;
			
			Float minFitness = 0F;
			Float maxFitness = Float.MAX_VALUE;
			
			Float normedFitness = 0F;
			Float fitnessInterval = 0F;
			
			//Precondition
			if (!(evaluatedPopulation != null && evaluatedPopulation.values().size() > 0)){
				throw new IllegalArgumentException("EvaluatedPopulation was null or empty!");
			}
			
			Integer probabilityToGetPicked = 0;
			
			Random random = new Random(System.currentTimeMillis());
			
			// Finde die maximale und minimal Fitness
			minFitness = Collections.min(evaluatedPopulation.values());
			maxFitness = Collections.max(evaluatedPopulation.values());
			
			fitnessInterval = maxFitness - minFitness;
			
			// Sollte das Fitness-Interval 0 sein, ist es nicht notwendig eine Selektion durchzuf�hren.
			if (fitnessInterval > 0F) {
			
				selectedPopulation = new ArrayList<SimplifiedPhysicalMaschine[]>();
			
				for (Map.Entry<SimplifiedPhysicalMaschine[], Float> individual : evaluatedPopulation.entrySet()){
					
					// Berechne die Fitness im Verh�ltnis zur minimalen Fitness
					normedFitness = individual.getValue() - minFitness;
					
					// Berechne �berlebenswahrscheinlichkeit
					probabilityToGetPicked = (int)(normedFitness / fitnessInterval * 100F);
					
					// Selektiere individuen
					if (random.nextInt(101) < probabilityToGetPicked){
						selectedPopulation.add(individual.getKey());
					}
				}
				
				// Wenn die Population zu klein ist, wird noch eine kleine Menge an Individuen hinzugef�gt.
				// Das verhindert, dass zuf�llig kein Individuum �berlebt.
				Iterator<SimplifiedPhysicalMaschine[]> population = evaluatedPopulation.keySet().iterator();

				while (selectedPopulation.size() < Math.min(minPopulationSize, evaluatedPopulation.size()) && population.hasNext()) {
					SimplifiedPhysicalMaschine[] individual = population.next();
					
					if (!selectedPopulation.contains(individual)){
						selectedPopulation.add(individual); 
					}
				}
			} else {
				// Keine Selektion vorgenommen.
				selectedPopulation = new ArrayList<SimplifiedPhysicalMaschine[]>(evaluatedPopulation.keySet());
			}
			
			//Post-Condition
			if (!(selectedPopulation != null && selectedPopulation.size() > 0)){
				throw new IllegalArgumentException("Selected population was null or empty!");
			}
			
			return selectedPopulation;
		}
		
		public ArrayList<SimplifiedPhysicalMaschine[]> reproduction(int populationSize, ArrayList<SimplifiedPhysicalMaschine[]> selectedPopulation){
			ArrayList<SimplifiedPhysicalMaschine[]> reproducedPopulation = null;
			
			Random random = new Random(System.currentTimeMillis());
			
			SimplifiedPhysicalMaschine[] randomIndividual = null;
			SimplifiedPhysicalMaschine[] mutant = null;
			
			SimplifiedPhysicalMaschine randomMachine1 = null;
			SimplifiedPhysicalMaschine randomMachine2 = null;
			
			Application app = null;
			
			//Pre-Condition
			if (!(selectedPopulation != null && selectedPopulation.size() > 0)){
				throw new IllegalArgumentException("Selected population was null or empty!");
			}
			
			// Es ist nicht notwendig die Population zu vergr��en, wenn noch alle Individuen vorhanden sind.
			if (populationSize > selectedPopulation.size())
			{
				// F�ge alle bisherigen Individuen zum Ergebnis hinzu
				reproducedPopulation = new ArrayList<SimplifiedPhysicalMaschine[]>(selectedPopulation);
				
				// Versuche soviel Mutationen, sodass im besten Fall die maximale Populationsgr��e erreicht ist.
				// Abbruchbedingung hinzugef�gt, damit die Schleife gestoppt wird, wenn es keine Individuen gibt,
				// die man mutieren k�nnte. (Bugfix)
				for (int i = 0; i < populationSize - selectedPopulation.size() && selectedPopulation.size() > 0; i++){
					// W�hle ein zuf�lliges Individuum aus und mutiere es.
					// Zus�tzlich wird es aus der Menge der verf�gbaren herausgenommen, damit es nicht zweimal
					// mutiert werden kann.
					randomIndividual = selectedPopulation.remove(random.nextInt(selectedPopulation.size()));
					
					mutant = randomIndividual.clone();
					
					// W�hlen eine zuf�llige Maschine aus
					randomMachine1 = mutant[random.nextInt(randomIndividual.length)];
					randomMachine2 = mutant[random.nextInt(randomIndividual.length)];
					
					if (randomMachine1 != null && randomMachine2 != null)
					{
						// Mutieren
						if (randomMachine1 != randomMachine2) {
							if (randomMachine1.canTakeRandomApplication()){
								app = randomMachine1.takeRandomApplication();
								
								if (randomMachine2.canAddApplication(app)){
									randomMachine2.addAppliction(app);
									
									// F�ge Mutant hinzu
									reproducedPopulation.add(mutant);
								} else {
									// Gebe die Applikation zur�ck
									randomMachine1.addAppliction(app);
								}
							}
						}
					} else {
						log.info("Random machine was null");
					}
				}
			} else {
				// Keine Reproduktion notwendig
				reproducedPopulation = new ArrayList<SimplifiedPhysicalMaschine[]>(selectedPopulation);
			}
			
			//Post-Condition
			if (!(reproducedPopulation != null && reproducedPopulation.size() > 0)){
				throw new IllegalArgumentException("Reproduced Population was null or empty!");
			}
			
			return reproducedPopulation;
		}
		
		public SimplifiedPhysicalMaschine[] getRandomIndividual(int currentlyUsedPMs, ArrayList<Application> applicationsToSchedule){
			SimplifiedPhysicalMaschine[] randomIndividual = null;
			SimplifiedPhysicalMaschine pm = null;
			Application app = null;
			Random random = new Random(System.currentTimeMillis());
			
			int randomIndex = 0;
			int maxRetries = currentlyUsedPMs;
			int currentRetry = 0;
			
			boolean appAssigned = false;
			boolean resourcesExhausted = false;
			
			// Initialisiere physikalische Maschinen
			randomIndividual = new SimplifiedPhysicalMaschine[currentlyUsedPMs];
			
			Iterator<Application> iterator = applicationsToSchedule.iterator();
			
			while(iterator.hasNext() && !resourcesExhausted) {
				// Hole app
				app = iterator.next();
				
				// Wiederholungen zur�cksetzen
				currentRetry = 0;
				
				// Abbruchbedingung zur�cksetzen
				appAssigned = false;
				
				while (currentRetry < maxRetries && !appAssigned)
				{
					// W�hle eine zuf�llige Maschine aus
					randomIndex = random.nextInt(currentlyUsedPMs);
					
					// Hole diese Maschine
					if (randomIndividual[randomIndex] == null){
						pm = new SimplifiedPhysicalMaschine();
						
						randomIndividual[randomIndex] = pm;
					} else {
						pm = randomIndividual[randomIndex];
					}
					
					// �berpr�fe ob genug Resourcen verf�gbar sind.
					if(pm.canAddApplication(app)) {
						pm.addAppliction(app);
						
						appAssigned = true;
					} else {
						currentRetry++;
						randomIndex = (randomIndex + 1) % currentlyUsedPMs;
					}
				}
				
				if (!appAssigned) {
					resourcesExhausted = true;
				}
			}
			
			if (resourcesExhausted) {
				return null;
			} else {
				return randomIndividual;
			}
		}
		
		public class SimplifiedPhysicalMaschine implements Cloneable {
			
			//vm init values
			private Integer initVmHdd = 100;
			private Integer initVmRam = 50;
			private Integer initVmCpu = 150;
			
			//pm init values
			private Integer initPmHdd = 850;
			private Integer initPmRam = 300;
			private Integer initPmCpu = 500;
			
			//pm values
			private Integer hdd = 50000;
			private Integer ram = 4700;
			private Integer cpu = 2400;
			
			//pm used values (usage of apps)
			private Integer usedHdd = 0;
			private Integer usedRam = 0;
			private Integer usedCpu = 0;
			
			private ArrayList<Application> apps = null;
			
			public SimplifiedPhysicalMaschine(){
				apps = new ArrayList<Application>();
			}
			
			public Boolean canAddApplication(Application app){
				return
					(hdd - getUsedHdd()) >= app.getHdd() &&
					(ram - getUsedRam()) >= app.getRam() &&
					(cpu - getUsedCpu()) >= app.getCpu();
			}
			
			public void addAppliction(Application app){
				apps.add(app);
				
				usedHdd += app.getHdd();
				usedRam += app.getRam();
				usedCpu += app.getCpu();
			}
			
			public Boolean canTakeRandomApplication(){
				return apps.size() > 0;
			}
			
			/**
			 * Takes (removes) a random application from the machine (simple)
			 * @return a random application
			 */
			public Application takeRandomApplication(){
				Random random = new Random(System.currentTimeMillis());
				
				Application app = null;
				
				app = apps.remove(random.nextInt(apps.size()));
				
				usedHdd -= app.getHdd();
				usedRam -= app.getRam();
				usedCpu -= app.getCpu();
				
				return app;
			}
			
			public ArrayList<Application> getApplications(){
				return apps;
			}
			
			public Integer getUsedHdd(){
				return initPmHdd + initVmHdd + usedHdd;
			}
			
			public Integer getUsedRam(){
				return initPmRam + initVmRam + usedRam;
			}
			
			public Integer getUsedCpu(){
				return initPmCpu + initVmCpu + usedCpu;
			}
			
			public Integer getHdd() {
				return hdd;
			}

			public Integer getRam() {
				return ram;
			}

			public Integer getCpu() {
				return cpu;
			}
			
			public Float getUtilizationOfHdd(){
				return (float)getUsedHdd() / (float)getHdd();
			}
			
			public Float getUtilizationOfRam(){
				return (float)getUsedRam() / (float)getRam();
			}
			
			public Float getUtilizationOfCpu(){
				return (float)getUsedCpu() / (float)getCpu();
			}
			
			@Override
			protected Object clone() {
				SimplifiedPhysicalMaschine clonedMachine = new SimplifiedPhysicalMaschine();
				
				clonedMachine.initVmHdd = this.initVmHdd;
				clonedMachine.initVmRam = this.initVmRam;
				clonedMachine.initVmCpu = this.initVmCpu;
				
				clonedMachine.hdd = this.hdd;
				clonedMachine.ram = this.ram;
				clonedMachine.cpu = this.cpu;
				
				
				clonedMachine.apps.addAll(this.apps);
				
				return clonedMachine;
			}
			
			@Override
			public String toString() {
				StringBuffer buffer = new StringBuffer();
				
				buffer.append("{ apps : [");
				for (Application app : apps){
					buffer.append(app.toString());
				}
				buffer.append("]}");
				
				return buffer.toString();
			}
		}
	}
}
